/*!
 * jQuery-Seat-Charts v1.1.5
 * https://github.com/mateuszmarkowski/jQuery-Seat-Charts
 *
 * Copyright 2013, 2016 Mateusz Markowski
 * Released under the MIT license
 */

		
	//'use strict';	
jQuery(document).ready(function () {
    jQuery.fn.extend({
        seatCharts: function (setup) {

            //if there's seatCharts object associated with the current element, return it
            if (this.data('seatCharts')) {
                return this.data('seatCharts');
            }

            var fn = this,
                seats = {},
                seatIds = [],
                legend,
                settings = {
                    animate: false, //requires jQuery UI
                    naming: {
                        top: true,
                        left: true,
                        getId: function (character, row, column) {
                            return row + '_' + column;
                        },
                        getLabel: function (character, row, column) {
                            return column;
                        }

                    },
                    legend: {
                        node: null,
                        items: []
                    },
                    click: function () {

                        if (this.status() == 'available') {
                            return 'selected';
                        } else if (this.status() == 'selected') {
                            return 'available';
                        } else {
                            return this.style();
                        }

                    },
                    focus: function () {

                        if (this.status() == 'available') {
                            return 'focused';
                        } else {
                            return this.style();
                        }
                    },
                    blur: function () {
                        return this.status();
                    },
                    seats: {}

                },
                //seat will be basically a seat object which we'll when generating the map
                seat = (function (seatCharts, seatChartsSettings) {
                    return function (setup) {
                        var fn = this;

                        fn.settings = $.extend({
                            status: 'available', //available, unavailable, selected
                            style: 'available',
                            //make sure there's an empty hash if user doesn't pass anything
                            data: seatChartsSettings.seats[setup.character] || {}
                            //anything goes here?
                        }, setup);

                        fn.settings.$node = $('<div></div>');

                        fn.settings.$node
                            .attr({
                                id: fn.settings.id,
                                role: 'checkbox',
                                'aria-checked': false,
                                focusable: true,
                                tabIndex: -1 //manual focus
                            })
                            .text(fn.settings.label)
                            .addClass(['seatCharts-seat', 'seatCharts-cell', 'available'].concat(
                                //let's merge custom user defined classes with standard JSC ones
                                fn.settings.classes,
                                typeof seatChartsSettings.seats[fn.settings.character] == "undefined" ?
                                    [] : seatChartsSettings.seats[fn.settings.character].classes
                                ).join(' '));

                        //basically a wrapper function
                        fn.data = function () {
                            return fn.settings.data;
                        };

                        fn.char = function () {
                            return fn.settings.character;
                        };

                        fn.node = function () {
                            return fn.settings.$node;
                        };

                        /*
                         * Can either set or return status depending on arguments.
                         *
                         * If there's no argument, it will return the current style.
                         *
                         * If you pass an argument, it will update seat's style
                         */
                        fn.style = function () {

                            return arguments.length == 1 ?
                                (function (newStyle) {
                                    var oldStyle = fn.settings.style;

                                    //if nothing changes, do nothing
                                    if (newStyle == oldStyle) {
                                        return oldStyle;
                                    }

                                    //focused is a special style which is not associated with status
                                    fn.settings.status = newStyle != 'focused' ? newStyle : fn.settings.status;
                                    fn.settings.$node
                                        .attr('aria-checked', newStyle == 'selected');

                                    //if user wants to animate status changes, let him do this
                                    seatChartsSettings.animate ?
                                        fn.settings.$node.switchClass(oldStyle, newStyle, 200) :
                                        fn.settings.$node.removeClass(oldStyle).addClass(newStyle);

                                    return fn.settings.style = newStyle;
                                })(arguments[0]) : fn.settings.style;
                        };

                        //either set or retrieve
                        fn.status = function () {

                            return fn.settings.status = arguments.length == 1 ?
                                fn.style(arguments[0]) : fn.settings.status;
                        };

                        //using immediate function to convienietly get shortcut variables
                        (function (seatSettings, character, seat) {
                            //attach event handlers
                            $.each(['click', 'focus', 'blur'], function (index, callback) {

                                //we want to be able to call the functions for each seat object
                                fn[callback] = function () {
                                    if (callback == 'focus') {
                                        //if there's already a focused element, we have to remove focus from it first
                                        if (seatCharts.attr('aria-activedescendant') !== undefined) {
                                            seats[seatCharts.attr('aria-activedescendant')].blur();
                                        }
                                        seatCharts.attr('aria-activedescendant', seat.settings.id);
                                        seat.node().focus();
                                    }

                                    /*
                                     * User can pass his own callback function, so we have to first check if it exists
                                     * and if not, use our default callback.
                                     *
                                     * Each callback function is executed in the current seat context.
                                     */
                                    return fn.style(typeof seatSettings[character][callback] === 'function' ?
                                        seatSettings[character][callback].apply(seat) : seatChartsSettings[callback].apply(seat));
                                };

                            });
                            //the below will become seatSettings, character, seat thanks to the immediate function		
                        })(seatChartsSettings.seats, fn.settings.character, fn);

                        fn.node()
                            //the first three mouse events are simple
                            .on('click', fn.click)
                            .on('mouseenter', fn.focus)
                            .on('mouseleave', fn.blur)

                            //keydown requires quite a lot of logic, because we have to know where to move the focus
                            .on('keydown', (function (seat, $seat) {

                                return function (e) {

                                    var $newSeat;

                                    //everything depends on the pressed key
                                    switch (e.which) {
                                        //spacebar will just trigger the same event mouse click does
                                        case 32:
                                            e.preventDefault();
                                            seat.click();
                                            break;
                                            //UP & DOWN
                                        case 40:
                                        case 38:
                                            e.preventDefault();

                                            /*
                                             * This is a recursive, immediate function which searches for the first "focusable" row.
                                             * 
                                             * We're using immediate function because we want a convenient access to some DOM elements
                                             * We're using recursion because sometimes we may hit an empty space rather than a seat.
                                             *
                                             */
                                            $newSeat = (function findAvailable($rows, $seats, $currentRow) {
                                                var $newRow;

                                                //let's determine which row should we move to

                                                if (!$rows.index($currentRow) && e.which == 38) {
                                                    //if this is the first row and user has pressed up arrow, move to the last row
                                                    $newRow = $rows.last();
                                                } else if ($rows.index($currentRow) == $rows.length - 1 && e.which == 40) {
                                                    //if this is the last row and user has pressed down arrow, move to the first row
                                                    $newRow = $rows.first();
                                                } else {
                                                    //using eq to get an element at the desired index position
                                                    $newRow = $rows.eq(
                                                        //if up arrow, then decrement the index, if down increment it
                                                        $rows.index($currentRow) + (e.which == 38 ? (-1) : (+1))
                                                    );
                                                }

                                                //now that we know the row, let's get the seat using the current column position
                                                $newSeat = $newRow.find('.seatCharts-seat,.seatCharts-space').eq($seats.index($seat));

                                                //if the seat we found is a space, keep looking further
                                                return $newSeat.hasClass('seatCharts-space') ?
                                                    findAvailable($rows, $seats, $newRow) : $newSeat;

                                            })($seat
                                                //get a reference to the parent container and then select all rows but the header
                                                    .parents('.seatCharts-container')
                                                    .find('.seatCharts-row:not(.seatCharts-header)'),
                                                $seat
                                                //get a reference to the parent row and then find all seat cells (both seats & spaces)
                                                    .parents('.seatCharts-row:first')
                                                    .find('.seatCharts-seat,.seatCharts-space'),
                                                //get a reference to the current row
                                                $seat.parents('.seatCharts-row:not(.seatCharts-header)')
                                            );

                                            //we couldn't determine the new seat, so we better give up
                                            if (!$newSeat.length) {
                                                return;
                                            }

                                            //remove focus from the old seat and put it on the new one
                                            seat.blur();
                                            seats[$newSeat.attr('id')].focus();
                                            $newSeat.focus();

                                            //update our "aria" reference with the new seat id
                                            seatCharts.attr('aria-activedescendant', $newSeat.attr('id'));

                                            break;
                                            //LEFT & RIGHT
                                        case 37:
                                        case 39:
                                            e.preventDefault();
                                            /*
                                             * The logic here is slightly different from the one for up/down arrows.
                                             * User will be able to browse the whole map using just left/right arrow, because
                                             * it will move to the next row when we reach the right/left-most seat.
                                             */
                                            $newSeat = (function ($seats) {

                                                if (!$seats.index($seat) && e.which == 37) {
                                                    //user has pressed left arrow and we're currently on the left-most seat
                                                    return $seats.last();
                                                } else if ($seats.index($seat) == $seats.length - 1 && e.which == 39) {
                                                    //user has pressed right arrow and we're currently on the right-most seat
                                                    return $seats.first();
                                                } else {
                                                    //simply move one seat left or right depending on the key
                                                    return $seats.eq($seats.index($seat) + (e.which == 37 ? (-1) : (+1)));
                                                }

                                            })($seat
                                                .parents('.seatCharts-container:first')
                                                .find('.seatCharts-seat:not(.seatCharts-space)'));

                                            if (!$newSeat.length) {
                                                return;
                                            }

                                            //handle focus
                                            seat.blur();
                                            seats[$newSeat.attr('id')].focus();
                                            $newSeat.focus();

                                            //update our "aria" reference with the new seat id
                                            seatCharts.attr('aria-activedescendant', $newSeat.attr('id'));
                                            break;
                                        default:
                                            break;

                                    }
                                };

                            })(fn, fn.node()));
                        //.appendTo(seatCharts.find('.' + row));

                    }
                })(fn, settings);

            fn.addClass('seatCharts-container');

            //true -> deep copy!
            $.extend(true, settings, setup);

            //Generate default row ids unless user passed his own
            settings.naming.rows = settings.naming.rows || (function (length) {
                var rows = [];
                for (var i = 1; i <= length; i++) {
                    rows.push(i);
                }
                return rows;
            })(settings.map.length);

            //Generate default column ids unless user passed his own
            settings.naming.columns = settings.naming.columns || (function (length) {
                var columns = [];
                for (var i = 1; i <= length; i++) {
                    columns.push(i);
                }
                return columns;
            })(settings.map[0].split('').length);

            if (settings.naming.top) {
                var $headerRow = $('<div></div>')
                    .addClass('seatCharts-row seatCharts-header');

                if (settings.naming.left) {
                    $headerRow.append($('<div></div>').addClass('seatCharts-cell'));
                }


                $.each(settings.naming.columns, function (index, value) {
                    $headerRow.append(
                        $('<div></div>')
                            .addClass('seatCharts-cell')
                            .text(value)
                    );
                });
            }

            fn.append($headerRow);

            //do this for each map row
            $.each(settings.map, function (row, characters) {

                var $row = $('<div></div>').addClass('seatCharts-row');

                if (settings.naming.left) {
                    $row.append(
                        $('<div></div>')
                            .addClass('seatCharts-cell seatCharts-space')
                            .text(settings.naming.rows[row])
                    );
                }

                /*
                 * Do this for each seat (letter)
                 *
                 * Now users will be able to pass custom ID and label which overwrite the one that seat would be assigned by getId and
                 * getLabel
                 *
                 * New format is like this:
                 * a[ID,label]a[ID]aaaaa
                 *
                 * So you can overwrite the ID or label (or both) even for just one seat.
                 * Basically ID should be first, so if you want to overwrite just label write it as follows:
                 * a[,LABEL]
                 *
                 * Allowed characters in IDs areL 0-9, a-z, A-Z, _
                 * Allowed characters in labels are: 0-9, a-z, A-Z, _, ' ' (space)
                 *
                 */

                $.each(characters.match(/[a-z_]{1}(\[[0-9a-z_]{0,}(,[0-9a-z_ ]+)?\])?/gi), function (column, characterParams) {
                    var matches = characterParams.match(/([a-z_]{1})(\[([0-9a-z_ ,]+)\])?/i),
                        //no matter if user specifies [] params, the character should be in the second element
                        character = matches[1],
                        //check if user has passed some additional params to override id or label
                        params = typeof matches[3] !== 'undefined' ? matches[3].split(',') : [],
                        //id param should be first
                        overrideId = params.length ? params[0] : null,
                        //label param should be second
                        overrideLabel = params.length === 2 ? params[1] : null;

                    $row.append(character != '_' ?
                        //if the character is not an underscore (empty space)
                        (function (naming) {

                            //so users don't have to specify empty objects
                            settings.seats[character] = character in settings.seats ? settings.seats[character] : {};

                            var id = overrideId ? overrideId : naming.getId(character, naming.rows[row], naming.columns[column]);
                            seats[id] = new seat({
                                id: id,
                                label: overrideLabel ?
                                    overrideLabel : naming.getLabel(character, naming.rows[row], naming.columns[column]),
                                row: row,
                                column: column,
                                character: character
                            });

                            seatIds.push(id);
                            return seats[id].node();

                        })(settings.naming) :
                        //this is just an empty space (_)
                        $('<div></div>').addClass('seatCharts-cell seatCharts-space')
                    );
                });

                fn.append($row);
            });

            //if there're any legend items to be rendered
            settings.legend.items.length ? (function (legend) {
                //either use user-defined container or create our own and insert it right after the seat chart div
                var $container = (legend.node || $('<div></div>').insertAfter(fn))
                    .addClass('seatCharts-legend');

                var $ul = $('<ul></ul>')
                    .addClass('seatCharts-legendList')
                    .appendTo($container);

                $.each(legend.items, function (index, item) {
                    $ul.append(
                        $('<li></li>')
                            .addClass('seatCharts-legendItem')
                            .append(
                                $('<div></div>')
                                    //merge user defined classes with our standard ones
                                    .addClass(['seatCharts-seat', 'seatCharts-cell', item[1]].concat(
                                        settings.classes,
                                        typeof settings.seats[item[0]] == "undefined" ? [] : settings.seats[item[0]].classes).join(' ')
                                    )
                            )
                            .append(
                                $('<span></span>')
                                    .addClass('seatCharts-legendDescription')
                                    .text(item[2])
                            )
                    );
                });

                return $container;
            })(settings.legend) : null;

            fn.attr({
                tabIndex: 0
            });


            //when container's focused, move focus to the first seat
            fn.focus(function () {
                if (fn.attr('aria-activedescendant')) {
                    seats[fn.attr('aria-activedescendant')].blur();
                }

                fn.find('.seatCharts-seat:not(.seatCharts-space):first').focus();
                seats[seatIds[0]].focus();

            });

            //public methods of seatCharts
            fn.data('seatCharts', {
                seats: seats,
                seatIds: seatIds,
                //set for one, set for many, get for one
                status: function () {
                    var fn = this;

                    return arguments.length == 1 ? fn.seats[arguments[0]].status() : (function (seatsIds, newStatus) {

                        return typeof seatsIds == 'string' ? fn.seats[seatsIds].status(newStatus) : (function () {
                            $.each(seatsIds, function (index, seatId) {
                                fn.seats[seatId].status(newStatus);
                            });
                        })();
                    })(arguments[0], arguments[1]);
                },
                each: function (callback) {
                    var fn = this;

                    for (var seatId in fn.seats) {
                        if (false === callback.call(fn.seats[seatId], seatId)) {
                            return seatId;//return last checked
                        }
                    }

                    return true;
                },
                node: function () {
                    var fn = this;
                    //basically create a CSS query to get all seats by their DOM ids
                    return $('#' + fn.seatIds.join(',#'));
                },

                find: function (query) {//D, a.available, unavailable
                    var fn = this;

                    var seatSet = fn.set();

                    //is RegExp
                    return query instanceof RegExp ?
                        (function () {
                            fn.each(function (id) {
                                if (id.match(query)) {
                                    seatSet.push(id, this);
                                }
                            });
                            return seatSet;
                        })() :
                        (query.length == 1 ?
                                (function (character) {
                                    //user searches just for a particual character
                                    fn.each(function () {
                                        if (this.char() == character) {
                                            seatSet.push(this.settings.id, this);
                                        }
                                    });

                                    return seatSet;
                                })(query) :
                                (function () {
                                    //user runs a more sophisticated query, so let's see if there's a dot
                                    return query.indexOf('.') > -1 ?
                                        (function () {
                                            //there's a dot which separates character and the status
                                            var parts = query.split('.');

                                            fn.each(function (seatId) {
                                                if (this.char() == parts[0] && this.status() == parts[1]) {
                                                    seatSet.push(this.settings.id, this);
                                                }
                                            });

                                            return seatSet;
                                        })() :
                                        (function () {
                                            fn.each(function () {
                                                if (this.status() == query) {
                                                    seatSet.push(this.settings.id, this);
                                                }
                                            });
                                            return seatSet;
                                        })();
                                })()
                        );

                },
                set: function set() {//inherits some methods
                    var fn = this;

                    return {
                        seats: [],
                        seatIds: [],
                        length: 0,
                        status: function () {
                            var args = arguments,
                                that = this;
                            //if there's just one seat in the set and user didn't pass any params, return current status
                            return this.length == 1 && args.length == 0 ? this.seats[0].status() : (function () {
                                //otherwise call status function for each of the seats in the set
                                $.each(that.seats, function () {
                                    this.status.apply(this, args);
                                });
                            })();
                        },
                        node: function () {
                            return fn.node.call(this);
                        },
                        each: function () {
                            return fn.each.call(this, arguments[0]);
                        },
                        get: function () {
                            return fn.get.call(this, arguments[0]);
                        },
                        find: function () {
                            return fn.find.call(this, arguments[0]);
                        },
                        set: function () {
                            return set.call(fn);
                        },
                        push: function (id, seat) {
                            this.seats.push(seat);
                            this.seatIds.push(id);
                            ++this.length;
                        }
                    };
                },
                //get one object or a set of objects
                get: function (seatsIds) {
                    var fn = this;

                    return typeof seatsIds == 'string' ?
                        fn.seats[seatsIds] : (function () {

                            var seatSet = fn.set();

                            $.each(seatsIds, function (index, seatId) {
                                if (typeof fn.seats[seatId] === 'object') {
                                    seatSet.push(seatId, fn.seats[seatId]);
                                }
                            });

                            return seatSet;
                        })();
                }
            });

            return fn.data('seatCharts');
        }

    });
});